package com.kevin.algorithm;

import com.kevin.model.LinearEquation;
import com.kevin.model.Location;
import com.kevin.utils.DistanceUtil;
import com.kevin.utils.PointsRep;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description: 通过起止点计算点是否落在根据起止点和半径组成的矩形内
 * 矩形生成方式：根据起止点形成线，然后通过垂直于起止点的线和距离起止点的半径生成矩形
 *
 * 判断点是否落入矩形区域内的算法：根据起止点生成垂直于起止点的两条线，然后判断点是否在两条线之间，并且点到起止点形成的线的距离小于等于半径
 *
 * @author kevin
 * @version V1.0
 * @date 2019/3/4 15:59
 **/
public class InPolygon implements AlgorithmStrategy{

    private PointsRep pointsRep;

    @Override
    public Location[][] execute(PointsRep pointsRep) {
        this.pointsRep = pointsRep;

        List<Location> result = new ArrayList<>();

        for(Location location : pointsRep.getPoints()){
            boolean flag = judgePointInPolygon(pointsRep,location);

            if(flag){
                result.add(location);
            }
        }

        /**
         * 封装返回结果
         */
        Location[][] locations = new Location[1][result.size()];
        for(int i=0;i<result.size();i++){
            locations[0][i] = result.get(i);
        }

        return locations;
    }

    /**
     * @Description: 根据经纬度点和半径获取矩形四个点
     *
     * @param
     * @return
     **/
    public boolean judgePointInPolygon(PointsRep pointsRep,Location location){
        /**
         * 根据开始点和结束点计算线性方程斜率和常量
         */
        LinearEquation linearEqua = getLinEquaByTwoPoint(pointsRep.getBegin(),pointsRep.getEnd());

        /**
         * 根据斜率和起点计算垂直于起止点线程的另一个线程
         */
        LinearEquation verLinEquaBegin = getVerLinEquaByPointAndSlope(pointsRep.getBegin(),linearEqua.getSlope());

        /**
         * 根据斜率和终点计算垂直于起止点线程的另一个线程
         */
        LinearEquation verLinEquaEnd = getVerLinEquaByPointAndSlope(pointsRep.getEnd(),linearEqua.getSlope());

        /**
         * 根据斜率和当前点计算垂直于起止点线程的另一个线程
         */
        LinearEquation verCurrLinEqua = getVerLinEquaByPointAndSlope(location,linearEqua.getSlope());

        /**
         * 根据当前点的线程和起止点线程计算相交点
         */
        Location intersection = getLocationByLinEquas(linearEqua,verCurrLinEqua);

        /**
         * 计算点是否在两个线程之间
         */
        boolean flag = judgePoint(verLinEquaBegin,verLinEquaEnd,intersection);

        /**
         * 判断两个点之间的距离是否在半径范围之内
         */
        double dis = Double.valueOf(DistanceUtil.algorithm(location.getLon(),location.getLat(),intersection.getLon(),intersection.getLat()));

        /**
         * 判断点是否在两个线程之间且距离在半径范围内
         */
        boolean result = false;
        if(flag && pointsRep.getRadius() >= dis){
            result = true;
        }

        return result;
    }


    /**
     * 根据两点计算两点形成的一维线性方程的斜率和常量
     * @return
     */
    private LinearEquation getLinEquaByTwoPoint(Location one , Location two){
        double x = one.getLon();
        double y = one.getLat();

        double x1 = two.getLon();
        double y1 = two.getLat();

        //斜率
        double a = (y-y1)/(x-x1);
        //常量
        double b = y-((y-y1)/(x-x1) * x);

        LinearEquation linearEquation = new LinearEquation();
        //斜率
        linearEquation.setSlope(a);
        //常量
        linearEquation.setConstant(b);

        return linearEquation;
    }

    /**
     * 根据线性方程斜率和点计算垂直于当前线性方程的另一个线性方程
     * @param location
     * @param slope
     * @return
     */
    private LinearEquation getVerLinEquaByPointAndSlope(Location location , double slope){
        double x = location.getLon();
        double y = location.getLat();

        //垂直于当前线程的另一个线程的斜率
        double a = (-1)/slope;
        //垂直于当前线程的另一个线程的常量
        double b = y - (a * x);

        LinearEquation linearEquation = new LinearEquation();
        //斜率
        linearEquation.setSlope(a);
        //常量
        linearEquation.setConstant(b);

        return linearEquation;
    }

    /**
     * 根据线性方程相交获取相交点
     * @return
     */
    private Location getLocationByLinEquas(LinearEquation one ,LinearEquation two){
        double a = one.getSlope();
        double b = one.getConstant();

        double a1 = two.getSlope();
        double b1 = two.getConstant();

        double x = (b1 - b)/(a - a1);
        double y = a * ( (b1-b)/(a-a1) ) + b;

        Location location = new Location();
        //经度
        location.setLon(x);
        //纬度
        location.setLat(y);

        return location;
    }

    /**
     * 根据两个平行的线性方程，判断点是否在两个线性方程之间
     * @return
     */
    private boolean judgePoint(LinearEquation one,LinearEquation two,Location location){
        //第一个线程
        double a = one.getSlope();
        double b = one.getConstant();
        //第二个线程
        double a1 = two.getSlope();
        double b1 = two.getConstant();

        //根据第一个线程和点的经度获取纬度
        double y = a * location.getLon() + b;

        //根据第二个线程和点的经度获取纬度
        double y1 = a1 * location.getLon() + b1;

        /**
         * 默认点不在两个方程之间
         */
        boolean result = false;

        if(b < b1){
            if(location.getLat() <= y1 && location.getLat() >= y){
                result = true;
            }
        }else{
            if(location.getLat() >= y1 && location.getLat() <= y){
                result = true;
            }
        }

        return result;
    }
}
